package com.szzz.gateway.listener;

import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.google.common.collect.Lists;
import com.szzz.gateway.properties.ApplicationProperties;
import com.szzz.gateway.properties.DynamicRouteProperties;
import com.szzz.gateway.route.DynamicRouting;
import com.szzz.gateway.route.entity.FilterEntity;
import com.szzz.gateway.route.entity.PredicateEntity;
import com.szzz.gateway.route.entity.RouteEntity;
import com.szzz.gateway.route.entity.RouteInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;

/**
 * 动态路由监听器
 *
 * @author ZhangYin
 */
@Slf4j
@Component
public class DynamicRoutingListener {

    private final DynamicRouting dynamicRouting;

    private final ApplicationProperties applicationProperties;

    public DynamicRoutingListener(DynamicRouting dynamicRouting, ApplicationProperties applicationProperties) {
        this.dynamicRouting = dynamicRouting;
        this.applicationProperties = applicationProperties;
    }

    /**
     * 项目启动时刷新路由, 添加路由监听器
     * @return {@}
     */
    @PostConstruct
    public void refresh() {
        try {
            // 1. 获取nacos配置
            DynamicRouteProperties routeProperties = applicationProperties.getRoute();
            String serverAddr = routeProperties.getServerAddr();
            String namespace  = routeProperties.getNamespace();
            String dataId     = routeProperties.getDataId();
            String group      = routeProperties.getGroup();
            // 2. 创建configService
            Properties properties = new Properties();
            properties.put(PropertyKeyConst.SERVER_ADDR, serverAddr);
            properties.put(PropertyKeyConst.NAMESPACE, namespace);
            ConfigService configService = NacosFactory.createConfigService(properties);
            // 3. 启动时初始化路由
            init(configService);

            // 4. 添加路由刷新监听器
            configService.addListener(dataId, group, new Listener() {
                @Override
                public Executor getExecutor() {
                    return null;
                }

                @Override
                public void receiveConfigInfo(String configInfo) {
                    // 1. 解析路由信息
                    RouteInfo routeInfo = JSON.parseObject(configInfo, RouteInfo.class);
                    // 2. 判断是否需要刷新路由
                    if (!routeInfo.isRefreshGatewayRoute()) {
                        return;
                    }
                    // 3. 刷新路由
                    Flux<RouteDefinition> routeDefinitions = parseRouteDefinition(routeInfo.getRoutes());
                    dynamicRouting.refresh(routeDefinitions);
                }
            });

        } catch (NacosException e) {
            log.error("Nacos 初始化路由失败", e);
        }
    }

    /**
     * 项目启动时,初始化路由
     * @param configService 配置中心
     * @throws NacosException exception
     */
    private void init(ConfigService configService) throws NacosException {
        // 1. 获取nacos配置
        DynamicRouteProperties routeProperties = applicationProperties.getRoute();
        // 2. 创建configService
        String routeInfoStr = configService.getConfig(routeProperties.getDataId(), routeProperties.getGroup(), routeProperties.getTimeout());
        // 3. 初始化路由
        RouteInfo routeInfo = JSON.parseObject(routeInfoStr, RouteInfo.class);
        List<RouteEntity> list = routeInfo.getRoutes();
        Flux<RouteDefinition> routeDefinitions = parseRouteDefinition(list);
        routeDefinitions.subscribe(dynamicRouting::add);
    }

    /**
     * 解析路由信息
     * @param routeEntities 路由
     * @return {@link RouteDefinition}
     */
    public Flux<RouteDefinition> parseRouteDefinition(List<RouteEntity> routeEntities) {
        List<RouteDefinition> routeDefinitions = new ArrayList<>();
        for (RouteEntity routeEntity : routeEntities) {
            RouteDefinition definition = new RouteDefinition();
            // 解析断言
            List<PredicateDefinition> predicates = Lists.newArrayList();
            for (PredicateEntity predicate : routeEntity.getPredicates()) {
                PredicateDefinition predicateDefinition = new PredicateDefinition();
                predicateDefinition.setArgs(predicate.getArgs());
                predicateDefinition.setName(predicate.getName());
                predicates.add(predicateDefinition);
            }
            // 解析拦截器
            List<FilterDefinition> filters = new ArrayList<>();
            for (FilterEntity filterEntity: routeEntity.getFilters()) {
                FilterDefinition filterDefinition = new FilterDefinition();
                filterDefinition.setArgs(filterEntity.getArgs());
                filterDefinition.setName(filterEntity.getName());
                filters.add(filterDefinition);
            }
            // 目标url
            URI uri = UriComponentsBuilder.fromUriString(routeEntity.getUri()).build().toUri();
            definition.setId(routeEntity.getId());
            definition.setPredicates(predicates);
            definition.setFilters(filters);
            definition.setUri(uri);
            definition.setOrder(routeEntity.getOrder());
            routeDefinitions.add(definition);
        }
        return Mono.just(routeDefinitions).flatMapMany(Flux::fromIterable);
    }

}
